2019 ng conf
Avoiding The Suck Of Testing Using Cypress.io | Joe Eames & Jesse Sanders
https://www.youtube.com/watch?v=GH9Dvo_BYkk&list=PLOETEcp3DkCpimylVKTDe968yNmNIajlR&index=29
這一集是Mike大大分享的Cypress.io,另外,NG-ZORRO超酷的,但不是測試就自行參考囉
https://www.youtube.com/watch?v=uNxkrTX_xmI&list=PL9LUW6O9WZqgUMHwDsKQf3prtqVvjGZ6S&index=22
Mike大大的文件
https://paper.dropbox.com/doc/print/wVmBd63ifvIkk7FI89Krt?print=true&noDesktopRedirect=1
建議可以再多看看官方文件
https://docs.cypress.io/zh-cn/guides/core-concepts/introduction-to-cypress.html#
Cypress建議大家可以試看看,因為e2e其實跟angular沒有什麼關係了
Cypress可以拿來測各種網站,完全可以獨立成一個測試專案
而且Cypress開Chrome的測試畫面好美喔缺點是他只能用Chrome
我覺得Cypress最大的缺點:會吃Chrome的版本
- 如果你不想升級你的angular專案,但你的chrome一直自動更新,你就會無法跑cypress。
- 如果你想固定開發環境,也是很困難的,你很難安裝某一版的chrome
- 所以cypress適合第三方套件用很少,Angular大改版就會升級的專案(UI可能用Angular Material才能確保版本同步)
首先,Mike大大對Cypress.io的優缺點分析
優勢
延申閱讀
(angular6時期的文章)
https://angularfirebase.com/lessons/cypress-angular-testing-end-to-end/
https://medium.com/@ronnieschaniel/getting-started-with-cypress-e2e-testing-in-angular-bc42186d913d
可以自動幫你裝cypress到你的angular專案
https://github.com/briebug/cypress-schematic
延伸閱讀:
對Schematic有興趣的人,可參考Leo大大的「高效 Coding 術:Angular Schematics 實戰三十天」
https://ithelp.ithome.com.tw/users/20090728/ironman/2149
玩轉 Schematics - Modern Web 2018
Mike大大的簡報
https://www.slideshare.net/ssuser35b57e/playing-schematics-modern-web-2018?fbclid=IwAR2o0aANF9U1eOVxOoyDCPkBWwYp10QvWtNySUXg7QdVDxmMIILVg1DaIJc
Mike大大寫的Schematics Snippets for VS Code
https://marketplace.visualstudio.com/items?itemName=MikeHuang.vscode-schematics-snippets&fbclid=IwAR3kwRx58vXmXQIBBWbZhDgnYWJil_IhEuHun0tIeh4IHMffz6PSdYWmWIs
開源
https://github.com/wellwind/vscode-schematics-snippets
幫angular專案加上cypress
npm install -g @briebug/cypress-schematic
# 在你的angular專案目錄
ng g @briebug/cypress-schematic:add
# 幫angular專案裝@briebug/cypress-schematic,及相依套件
ng add @briebug/cypress-schematic
其他網路找的範例程式
比較新一點的cypress的文章(Angular 7的),以Login為例
因為時間的關係,我就不找Angular 8的囉,不過7升8難度應該較低
https://blog.angularindepth.com/get-started-with-cypress-d6ac4b910605
原始碼
https://github.com/melcor76/angular-7-registration-login-example-cli
npm install
ng e2e
# cypress會發現會吃chrome的版本
# 如果你的cypress版本太舊,會這樣
# 2019/9/28,我的chrome version為77,就無法跑cypress
[06:30:17] E/launcher - session not created: Chrome version must be between 71 and 75
# 如果有新版的cypress,會提醒你更新
# Version 3.4.1 is now available (currently running Version 3.2.0)
# To update Cypress:
# Quit this app.
# If using npm, run npm install --save-dev cypress@3.4.1
# If using yarn, run yarn add cypress@3.4.1
# Run node_modules/.bin/cypress open to open the new version.
# 更新cypress
npm install --save-dev cypress@3.4.1
ng test 跑執行單元測試(Karma)
# 因為這個專案的package.json的"scripts"裡沒有定義e2e
# 所以不能下 ng e2e 喔
cypress開chrome的測試畫面真的很美
Commands drive your tests in the browser like a real user would. They let you perform actions like typing, clicking, xhr requests, and can also assert things like "my button should be disabled".
用cypress來測試cypress doc
原始碼
https://github.com/cypress-io/cypress-example-kitchensink
要測的網站
https://example.cypress.io
整個example.cypress.io很大,會跑很久喔
# 安裝packages
npm i
# 執行測試方式1
npm start # 把網站跑起來
npm run cy:open # 執行cypress test runner
# 執行測試方式2
npm run local:run # 定義在package.json的sciprts裡 "local:run": "start-test 8080 cy:run",
這個範例會在背景執行chrome,會把結果錄成mp4放在cypress/videos/
cypress/fixtures
測試過程中需要用到的外部靜態資料,通常會使用cy.fixture(),
常用於儲存http request
cypress/integration
測試案例寫在這裡,支援.js, .jsx.coffee, .cjsx
也支援ES2015 modules、CommonJS moduleshttps://github.com/cypress-io/cypress-example-recipes/blob/master/examples/fundamentals__node-modules/cypress/integration/es2015-commonjs-modules-spec.js
context()與describe()相同、而define()與it()相同
cypress/plugins/index.js
在跑spec.js前都會include的
https://on.cypress.io/plugins-guide
const _ = require('lodash') // yup, dev dependencies
const path = require('path') // yup, built in node modules
const debug = require('debug') // yup, dependencies
const User = require('../../lib/models/user') // yup, relative local modules
Cypress.Commands.add(name, callbackFn)
Cypress.Commands.add(name, options, callbackFn)
Cypress.Commands.overwrite(name, callbackFn)
Cypress.Commands.overwrite(name, options, callbackFn)
// 幫cy加個login()
Cypress.Commands.add('login', (userType, options = {}) => {
^^name ^^^^^^^^^^^^^^^^^^^^^^^^callbackFn
// this is an example of skipping your UI and logging in programmatically
// 定義2種user
const types = {
admin: {
name: 'Jane Lane',
admin: true,
},
user: {
name: 'Jim Bob',
admin: false,
}
}
// 抓出是admin或user
const user = types[userType]
// 新增一筆user
cy.request({
url: '/seed/users', // assuming you've exposed a seeds route
method: 'POST',
body: user,
})
.its('body')
.then((body) => {
// (body)是假設server回傳的資料,包含:body.email, body.password
cy.request({
url: '/login',
method: 'POST',
body: {
email: body.email,
password: body.password,
}
})
})
})
在spec.js裡使用login
cy.login('admin')
cy
.get('button')
.login('user') // 可以串接,但不會收到前一個subject
touch {your_project}/cypress/integration/sample_spec.js
# 寫完spec.js後,打開cypress就可以看到sample_spec.js
./node_modules/.bin/cypress open
依照教學文件,玩測試案例
/cypress/integration/sample_spec.js
describe('My First Test', function() {
it('測試會通過', function() {
expect(true).to.equal(true) // 預期true等於true
})
it('測試會失敗', function() {
expect(true).to.equal(false)
})
it('Visits the Kitchen Sink', function() {
// 開啟https://example.cypress.io
cy.visit('https://example.cypress.io')
// 找到包含type的elements,並click()
// 點下type後,網址會跳到https://example.cypress.io/commands/actions
cy.contains('type').click()
// 網址列包含'/commands/actions'
cy.url().should('include', '/commands/actions')
^^^^ 取得瀏覽器目前的路由
// Get an input, type into it and verify that the value has been updated
// selector找到input的class=.action-email的
// 輸入fake@email.com
// 輸入後,input值為'fake@email.com'
cy.get('.action-email')
.type('fake@email.com')
.should('have.value', 'fake@email.com')
})
})
官方文件提到,cypress是用來測自己的專案,不是拿來當爬蟲的
有些網站基於安全,會阻擋cypress
這個範例好在readme.md就教你玩CI/CD
fork出repository到你的github,CI跑完,再CD到你自已fork出來的repository
https://github.com/cypress-io/cypress-example-todomvc
還好不一定要玩一整套CI/CI,就可以
npm i
npm start # 跑http://localhost:8888
./node_modules/.bin/cypress open # 開啟cypress
{
"baseUrl": "http://localhost:8888"
}
// 定義Cypress object "cy"
/// <reference types="cypress" />
// 自定義指令,例如 "createDefaultTodos"
/// <reference types="../support" />
// 檢查可不可以使用TypeScript
// @ts-check
// ***********************************************
// 用 Cypress tests 來重寫官網的 TodoMVC tests (使用Selenium)
// getting started guide
// https://on.cypress.io/introduction-to-cypress
我們可以發現Cypress提供很多function,跟其他e2e框架不同的語法,要再去熟練
describe('TodoMVC - React', function () {
// 定義變數
let TODO_ITEM_ONE = 'buy some cheese'
let TODO_ITEM_TWO = 'feed the cat'
let TODO_ITEM_THREE = 'book a doctors appointment'
beforeEach(function () {
// Cypress預設每次測試都會清掉Local Storage
// 每次都回到cypress.json定義的http://localhost:8888
// https://on.cypress.io/api/visit
cy.visit('/')
})
// a very simple example helpful during presentations
it('adds 2 todos', function () {
cy.get('.new-todo') // selector,找到class為new-todo的element
.type('learn testing{enter}') // 輸入learn testing,按enter
.type('be cool{enter}') // 輸入be cool,按enter
// 此時符合.todo-list li的elements應該有2個
cy.get('.todo-list li').should('have.length', 2)
cy.get('.mobile-nav', { timeout: 10000 }) // 設timeout
.should('be.visible') // 應該要可以看的到的
.and('contain', 'Home') // 而且包含'Home'
})
context('When page is initially opened', function () {
it('should focus on the todo input field', function () {
// http://on.cypress.io/focused
// 當頁面一打開後,預期判斷focused的element的class會有.new-todo
cy.focused().should('have.class', 'new-todo')
})
})
context('No Todos', function () {
it('should hide #main and #footer', function () {
// http://on.cypress.io/get
// 下列3個selector應該都要抓不到element
cy.get('.todo-list li').should('not.exist')
cy.get('.main').should('not.exist')
cy.get('.footer').should('not.exist')
})
})
context('New Todo', function () {
// New commands used here:
// https://on.cypress.io/type
// https://on.cypress.io/eq
// https://on.cypress.io/find
// https://on.cypress.io/contains
// https://on.cypress.io/should
// https://on.cypress.io/as
it('should allow me to add todo items', function () {
// create 1st todo,建第1筆todo
cy.get('.new-todo').type(TODO_ITEM_ONE).type('{enter}')
// 建完後,第1筆todo的label應該包含剛剛輸入的TODO_ITEM_ONE
cy.get('.todo-list li').eq(0).find('label').should('contain', TODO_ITEM_ONE)
// create 2nd todo
cy.get('.new-todo').type(TODO_ITEM_TWO).type('{enter}')
...
})
it('adds items', function () {
// 輸入4筆todo,應該要有4筆
cy.get('.new-todo')
.type('todo A{enter}')
.type('todo B{enter}') // we can continue working with same element
.type('todo C{enter}') // and keep adding new items
.type('todo D{enter}')
cy.get('.todo-list li').should('have.length', 4)
})
it('should clear text input field when an item is added', function () {
// 當.new-todo輸入完後,text應該恢復為空字串''
cy.get('.new-todo').type(TODO_ITEM_ONE).type('{enter}')
cy.get('.new-todo').should('have.text', '')
})
it('should append new items to the bottom of the list', function () {
// defined in cypress/support/commands.js
cy.createDefaultTodos().as('todos')
^^^^^^^^^^^^^^^^^^^自定義的function,可重複使用
cy.get('.todo-count').contains('3 items left')
cy.get('@todos').eq(0).find('label').should('contain', TODO_ITEM_ONE)
cy.get('@todos').eq(1).find('label').should('contain', TODO_ITEM_TWO)
cy.get('@todos').eq(2).find('label').should('contain', TODO_ITEM_THREE)
^ Alias
// https://docs.cypress.io/guides/core-concepts/variables-and-aliases.html#Stale-Elements
})
...
it('should show #main and #footer when items added', function () {
cy.createTodo(TODO_ITEM_ONE) // 輸入完1個todo後
^^^^^^^^^ 自定義的function
cy.get('.main').should('be.visible') // 應該要有.main
cy.get('.footer').should('be.visible') // 應該要有.footer
})
})
...其他的都類似,k完並熟練就好囉